use app::*;
use axum::{
    async_trait, extract::{ FromRef, FromRequestParts, Host, Query, State}, http::{header::{HeaderMap, SET_COOKIE}, request::Parts, StatusCode}, response::{IntoResponse, Json, Redirect, Response}, Form, RequestPartsExt
};

use axum::{
    error_handling::HandleErrorLayer,
    extract::DefaultBodyLimit,    
    response::Html,
    body::Body,
    http::{Request, Uri},
    routing::{get, post, Router},
};
use axum::response::Result as AResult;

use serde::{Deserialize, Serialize, *};
use fileserv::file_and_error_handler;
use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use std::path::{self, PathBuf};
use tower_http::services::ServeDir;
use tower::{BoxError, ServiceBuilder};
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::trace::TraceLayer;
use std::time::Duration;
use std::borrow::Cow;
use std::sync::Arc;
use tokio;
use tokio::signal;
use tower::ServiceExt;
use anyhow::{bail, Result};
use chrono::prelude::*;
use common::{Config, Task, SearchParams, SubFutureInfo};
use clap::Parser;
use tracing::{debug, error, info, warn};


pub mod fileserv;
mod api;
mod db;
mod shared;
mod server;
mod shutdown;
mod worker;

use api::*;
use db::*;
use shared::*;
use server::*;
use shutdown::*;
use worker::Worker;

type SharedHandle = Arc<Shared>;
/* 
pub struct Shared {    
    pub timestamp : std::sync::RwLock<String>,
    pub dataset : std::sync::RwLock< Vec<Task> >,
    pub site_root: String,
}
*/ 
// use axum::extract::FromRef;

#[derive(Parser, Debug)]
#[clap(
    name = "loong",
    version,
    author = "lengss",
    about = "tools for futures!"
)]
pub struct Cli {
    /// Sets a custom trace filter, such as info|debug|error|...
    #[clap(name = "trace", short('e'), long, default_value = "error")]
    trace: String,
    #[clap(name = "config", short, long, default_value = "./config.json")]
    config: String,
}

#[tokio::main]

async fn main() -> Result<()> {   
    let cli = Cli::parse();
    // tracelog_init(&cli.trace);
    let conf = get_configuration(None).await.unwrap();
    debug!("{:?}", conf);
    info!("all are ok, then start service ...");
    if let Err(err) = server::startup(cli).await{
        error!("{:#?}", err);
    }
    Ok(())
}



async fn axum_main(cfg:&Config, shared: Arc<Shared> )-> Result<()> {

    let logpath = PathBuf::from(&cfg.site_root).join("logs");
    if logpath.exists() == false {
         std::fs::create_dir_all(logpath).unwrap();
    }    
    let dbpath = PathBuf::from(&cfg.site_root).join("db");
    if dbpath.exists() == false {
         std::fs::create_dir_all(dbpath).unwrap();
    }

    simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");

    // Setting get_configuration(None) means we'll be using cargo-leptos's env values
    // For deployment these variables are:
    // <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
    // Alternately a file can be specified such as Some("Cargo.toml")
    // The file would need to be included with the executable when moved to deployment
    let addr = format!("0.0.0.0:{}", cfg.site_port);
    let mut conf = get_configuration(None).await.unwrap();
        
    conf.leptos_options.output_name = cfg.pkg_name.clone();   
    conf.leptos_options.site_root = cfg.site_root.clone(); 
    conf.leptos_options.site_pkg_dir = cfg.site_pkg.clone(); 
    conf.leptos_options.site_addr = addr.parse().unwrap();
      
    let leptos_options = conf.leptos_options;
    let routes = generate_route_list(App);

    let assets_dir = PathBuf::from(&cfg.site_root); //   leptos_options.site_root.as_str());
    //let server_uri = "http://192.168.2.100:3000";
   // let shared = Shared::new(cfg_file, &cfg.server_uri, &cfg.site_root , dbpool);
   // let shared: Arc<Shared> = Arc::new( shared );
  
    // build our application with a route
    let app = Router::new()    
    // 特别注意，axum的路由等功能注册，都必须在leptos_axum之前，否则会报错！！！
        .route("/post_hello", post(post_todo))
        .route("/get_hello", get(get_hello))
        .route("/loong/api/get_config", get(api_get_config))
        .route("/axum/post_task", post(api_post_task))
        .route("/axum/get_task", get(api_get_task))
        .route("/favicon.ico", get( spec_res_handler) )
        .with_state(Arc::clone(&shared))
        .layer(
            ServiceBuilder::new()
                // Handle errors from middleware
                .layer(HandleErrorLayer::new(handle_error))
                .load_shed()
                .concurrency_limit(1024)
                .timeout(Duration::from_secs(5))
                .layer(TraceLayer::new_for_http())
                .into_inner(),
        )
        .layer(DefaultBodyLimit::disable())
        .layer(RequestBodyLimitLayer::new(
            10 * 1024 * 1024, /* 250mb */
        ))              
        .nest_service("/site", ServeDir::new(assets_dir))    
        // 此处之下，才是leptos_axum注册的！！！
        //.leptos_routes(&leptos_options, routes, App)  
        .leptos_routes_with_context(
            &leptos_options,
            routes,
            {
                let app_state = leptos_options.clone();
                move || provide_context(app_state.clone() )
            },
            App,
        )
        .fallback(file_and_error_handler)
        .with_state(leptos_options);     
       
    // run our app with hyper
    // `axum::Server` is a re-export of `hyper::Server`
    log::info!("listening on http://{}", &addr);
    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
     
    /*  
    axum::serve(listener, app.into_make_service())
        .await
        .unwrap();    
    */ 
    let r = axum::serve(listener, app.into_make_service())
        // 因为被leptos 封装，这里的with_graceful_shutdown实际并不起作用！！！！
        .with_graceful_shutdown(shutdown_signal("auxum server"))
        .await;

    if let std::result::Result::Err(err) = r {
        //println!("{:#?}", err);
        bail!("create auxum web server failed {:?}", err);
    } 
    Ok(())
}

async fn shutdown_signal(name: &str) {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }   
}


async fn handle_error(error: BoxError) -> impl IntoResponse {
    if error.is::<tower::timeout::error::Elapsed>() {
        return (
            StatusCode::REQUEST_TIMEOUT,
            Cow::from("request timed out  [ handle_error ]"),
        );
    }

    if error.is::<tower::load_shed::error::Overloaded>() {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Cow::from("service is overloaded, try again later  [ handle_error ]"),
        );
    }

    (
        StatusCode::INTERNAL_SERVER_ERROR,
        Cow::from(format!(
            "!!!!!!!!!! Unhandled internal error: {} [ handle_error ]",
            error
        )),
    )
}

async fn spec_res_handler(  uri: Uri, State(shared): State<SharedHandle>,) ->  AResult<Response> {
    logging::log!("favicon_handler: {}/{}", shared.cfg.site_root, uri);
    let site =  path::PathBuf::from(&shared.cfg.site_root);    
    let req = Request::builder()
        .uri(uri.clone())
        .body(Body::empty())
        .unwrap();
    let res = ServeDir::new(&site).oneshot(req).await?;
    return Ok(res.map(Body::new).into_response());
}

